/*
 * Based on arch/arm/kernel/process.c
 *
 * Original Copyright (C) 1995  Linus Torvalds
 * Copyright (C) 1996-2000 Russell King - Converted to ARM.
 * Copyright (C) 2012 ARM Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <seminix/cache.h>
#include <seminix/percpu.h>
#include <seminix/sched.h>
#include <seminix/cpu.h>
#include <seminix/reboot.h>
#include <seminix/dump_stack.h>
#include <asm-generic/switch_to.h>
#include <asm/ptrace.h>
#include <asm/system_misc.h>
#include <asm/sysreg.h>
#include <asm/mmu_context.h>
#include <asm/fpsimd.h>
#include <asm/stacktrace.h>

#if defined(CONFIG_STACKPROTECTOR)
#include <seminix/stackprotector.h>
unsigned long __stack_chk_guard __read_mostly;
#endif

/*
 * Function pointers to optional machine specific functions
 */
void (*pm_power_off)(void);

void (*arm_pm_restart)(enum reboot_mode reboot_mode, const char *cmd);

/*
 * This is our default idle handler.
 */
void arch_cpu_idle(void)
{
    /*
     * This should do all the clock switching and wait for interrupt
     * tricks
     */
    cpu_do_idle();
    local_irq_enable();
}

/*
 * Halting simply requires that the secondary CPUs stop performing any
 * activity (executing tasks, handling interrupts). smp_send_stop()
 * achieves this.
 */
void machine_halt(void)
{
    local_irq_disable();
    smp_send_stop();
    while (1);
}

/*
 * Power-off simply requires that the secondary CPUs stop performing any
 * activity (executing tasks, handling interrupts). smp_send_stop()
 * achieves this. When the system power is turned off, it will take all CPUs
 * with it.
 */
void machine_power_off(void)
{
    local_irq_disable();
    smp_send_stop();
    if (pm_power_off)
        pm_power_off();
}

/*
 * Restart requires that the secondary CPUs stop performing any activity
 * while the primary CPU resets the system. Systems with multiple CPUs must
 * provide a HW restart implementation, to ensure that all CPUs reset at once.
 * This is required so that any code running after reset on the primary CPU
 * doesn't have to co-ordinate with other CPUs to ensure they aren't still
 * executing pre-reset code, and using RAM that the primary CPU's code wishes
 * to use. Implementing such co-ordination would be essentially impossible.
 */
void machine_restart(char *cmd)
{
    /* Disable interrupts first */
    local_irq_disable();
    smp_send_stop();

    /* Now call the architecture specific reboot code. */
    if (arm_pm_restart)
        arm_pm_restart(reboot_mode, cmd);

    /*
     * Whoops - the architecture was unable to reboot.
     */
    printk("Reboot failed -- System halted\n");
    while (1);
}

static void print_pstate(struct pt_regs *regs)
{
    u64 pstate = regs->pstate;

    if (compat_user_mode(regs)) {
        printk("pstate: %08llx (%c%c%c%c %c %s %s %c%c%c)\n",
            pstate,
            pstate & PSR_AA32_N_BIT ? 'N' : 'n',
            pstate & PSR_AA32_Z_BIT ? 'Z' : 'z',
            pstate & PSR_AA32_C_BIT ? 'C' : 'c',
            pstate & PSR_AA32_V_BIT ? 'V' : 'v',
            pstate & PSR_AA32_Q_BIT ? 'Q' : 'q',
            pstate & PSR_AA32_T_BIT ? "T32" : "A32",
            pstate & PSR_AA32_E_BIT ? "BE" : "LE",
            pstate & PSR_AA32_A_BIT ? 'A' : 'a',
            pstate & PSR_AA32_I_BIT ? 'I' : 'i',
            pstate & PSR_AA32_F_BIT ? 'F' : 'f');
    } else {
        printk("pstate: %08llx (%c%c%c%c %c%c%c%c %cPAN %cUAO)\n",
            pstate,
            pstate & PSR_N_BIT ? 'N' : 'n',
            pstate & PSR_Z_BIT ? 'Z' : 'z',
            pstate & PSR_C_BIT ? 'C' : 'c',
            pstate & PSR_V_BIT ? 'V' : 'v',
            pstate & PSR_D_BIT ? 'D' : 'd',
            pstate & PSR_A_BIT ? 'A' : 'a',
            pstate & PSR_I_BIT ? 'I' : 'i',
            pstate & PSR_F_BIT ? 'F' : 'f',
            pstate & PSR_PAN_BIT ? '+' : '-',
            pstate & PSR_UAO_BIT ? '+' : '-');
    }
}

void __show_regs(struct pt_regs *regs)
{
    int i, top_reg;
    u64 lr, sp;

    if (compat_user_mode(regs)) {
        lr = regs->compat_lr;
        sp = regs->compat_sp;
        top_reg = 12;
    } else {
        lr = regs->regs[30];
        sp = regs->sp;
        top_reg = 29;
    }

    show_regs_print_info(KERN_DEFAULT);
    print_pstate(regs);

    if (!user_mode(regs)) {
        printk("pc : %pS\n", (void *)regs->pc);
        printk("lr : %pS\n", (void *)lr);
    } else {
        printk("pc : %016llx\n", regs->pc);
        printk("lr : %016llx\n", lr);
    }

    printk("sp : %016llx\n", sp);

    i = top_reg;

    while (i >= 0) {
        printk("x%-2d: %016llx ", i, regs->regs[i]);
        i--;

        if (i % 2 == 0) {
            pr_cont("x%-2d: %016llx ", i, regs->regs[i]);
            i--;
        }

        pr_cont("\n");
    }
}

void show_regs(struct pt_regs * regs)
{
    __show_regs(regs);
    dump_backtrace(regs, NULL);
}

static void tls_thread_flush(void)
{
    write_sysreg(0, tpidr_el0);
}

void flush_thread(void)
{
    fpsimd_flush_thread();
    tls_thread_flush();
}

void release_thread(struct task_struct *dead_task)
{
}

void arch_release_task_struct(struct task_struct *tsk)
{
    fpsimd_release_task(tsk);
}

/*
 * src and dst may temporarily have aliased sve_state after tcb
 * is copied.  We cannot fix this properly here, because src may have
 * live SVE state and dst's thread_info may not exist yet, so tweaking
 * either src's or dst's TIF_SVE is not safe.
 *
 * The unaliasing is done in copy_thread() instead.  This works because
 * dst is not schedulable or traceable until both of these functions
 * have been called.
 */
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
{
    if (current->mm)
        fpsimd_preserve_current_state();
    *dst = *src;

    return 0;
}

asmlinkage void ret_from_fork(void) asm("ret_from_fork");

int copy_thread(unsigned long clone_flags, unsigned long stack_start,
        unsigned long stk_sz, struct task_struct *p)
{
    struct pt_regs *childregs = task_pt_regs(p);

    memset(&p->thread.cpu_context, 0, sizeof (struct cpu_context));

    /*
     * Unalias p->thread.sve_state (if any) from the parent task
     * and disable discard SVE state for p:
     */
    clear_tsk_thread_flag(p, TIF_SVE);
    p->thread.sve_state = NULL;

    /*
     * In case p was allocated the same tcb pointer as some
     * other recently-exited task, make sure p is disassociated from
     * any cpu that may have run that now-exited task recently.
     * Otherwise we could erroneously skip reloading the FPSIMD
     * registers for p.
     */
    fpsimd_flush_task_state(p);

    if (likely(!(p->flags & PF_KTHREAD))) {
        *childregs = *current_pt_regs();
        childregs->regs[0] = 0;

        /*
         * Read the current TLS pointer from tpidr_el0 as it may be
         * out-of-sync with the saved value.
         */
        *task_user_tls(p) = read_sysreg(tpidr_el0);

        if (stack_start)
            childregs->sp = stack_start;

        /*
         * If a TLS pointer was passed to clone (4th argument), use it
         * for the new thread.
         */
        if (clone_flags & CLONE_SETTLS)
            p->thread.uw.tp_value = childregs->regs[3];
    } else {
        memset(childregs, 0, sizeof(struct pt_regs));
        childregs->pstate = PSR_MODE_EL1h;
        if (IS_ENABLED(CONFIG_ARM64_UAO) &&
            cpus_have_const_cap(ARM64_HAS_UAO))
            childregs->pstate |= PSR_UAO_BIT;

        if (arm64_get_ssbd_state() == ARM64_SSBD_FORCE_DISABLE)
            childregs->pstate |= PSR_SSBS_BIT;

        p->thread.cpu_context.x19 = stack_start;
        p->thread.cpu_context.x20 = stk_sz;
    }
    p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
    p->thread.cpu_context.sp = (unsigned long)childregs;

    return 0;
}

void tls_preserve_current_state(void)
{
    *task_user_tls(current) = read_sysreg(tpidr_el0);
}

static void tls_thread_switch(struct task_struct *next)
{
    tls_preserve_current_state();

    if (!arm64_kernel_unmapped_at_el0())
        write_sysreg(0, tpidrro_el0);

    write_sysreg(*task_user_tls(next), tpidr_el0);
}

DEFINE_PER_CPU(struct task_struct *, __entry_task);

static void entry_task_switch(struct task_struct *next)
{
    __this_cpu_write(__entry_task, next);
}

/*
 * Thread switching.
 */
struct task_struct *__switch_to(struct task_struct *prev, struct task_struct *next)
{
    struct task_struct *last;

    fpsimd_thread_switch(next);
    tls_thread_switch(next);
    contextidr_thread_switch(next);
    entry_task_switch(next);

    /*
     * Complete any pending TLB or cache maintenance on this CPU in case
     * the thread migrates to a different CPU.
     * This full barrier is also required by the membarrier system
     * call.
     */
    dsb(ish);

    /* the actual thread switch */
    last = cpu_switch_to(prev, next);

    return last;
}
